use crate::{prelude::*, Allocator, Error, NullAlloc, PoolAlloc};
use core::alloc::Layout;
use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd};
use core::convert::{AsMut, AsRef};
use core::fmt::{self, Debug, Display, Formatter};
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::mem::{needs_drop, ManuallyDrop, MaybeUninit};
use core::ops::{Deref, DerefMut};
use core::ptr::{self, NonNull};
use core::slice;

#[repr(C)]
pub struct Boxed<'a, T: ?Sized + 'a, A: Allocator = PoolAlloc> {
    ptr: NonNull<T>,
    layout: Layout,
    alloc: A,
    mark1: PhantomData<T>,
    mark2: PhantomData<&'a ()>,
}

unsafe impl<T: ?Sized + Send, A: Allocator + Send> Send for Boxed<'static, T, A> {}
unsafe impl<T: ?Sized + Sync, A: Allocator> Sync for Boxed<'_, T, A> {}

impl<T> Boxed<'static, T, PoolAlloc> {
    pub fn new(val: T) -> Result<Self, Error> {
        Self::new_in(PoolAlloc, val)
    }

    pub fn new_then<F>(f: F) -> Result<Self, Error>
    where
        F: FnOnce(&mut MaybeUninit<T>) -> Result<(), Error>,
    {
        Self::new_then_in(PoolAlloc, f)
    }

    pub fn new_with_buf<F>(layout: Layout, f: F) -> Result<Self, Error>
    where
        F: FnOnce(&mut MaybeUninit<T>, NonNull<[u8]>) -> Result<(), Error>,
    {
        Self::new_with_buf_in(PoolAlloc, layout, f)
    }
}

impl Boxed<'static, (), PoolAlloc> {
    pub fn init_then<U, F>(f: F) -> Result<Boxed<'static, U, PoolAlloc>, Error>
    where
        F: FnOnce(&mut MaybeUninit<U>) -> Result<(), Error>,
    {
        Boxed::new_then(f)
    }
    pub fn init_slice_then<U, F>(len: usize, f: F) -> Result<Boxed<'static, [U], PoolAlloc>, Error>
    where
        F: FnMut(usize, &mut MaybeUninit<U>) -> Result<(), Error>,
    {
        Boxed::new_slice_then(len, f)
    }
    pub fn init_with_buf<U, F>(layout: Layout, f: F) -> Result<Boxed<'static, U, PoolAlloc>, Error>
    where
        F: FnOnce(&mut MaybeUninit<U>, NonNull<[u8]>) -> Result<(), Error>,
    {
        Boxed::new_with_buf(layout, f)
    }

    pub fn zeroed<U>() -> Result<Boxed<'static, MaybeUninit<U>, PoolAlloc>, Error> {
        Self::zeroed_in::<U>(PoolAlloc)
    }
    pub fn zeroed_slice<U>(
        len: usize,
    ) -> Result<Boxed<'static, [MaybeUninit<U>], PoolAlloc>, Error> {
        Self::zeroed_slice_in::<U>(PoolAlloc, len)
    }
    pub fn uninit<U>() -> Result<Boxed<'static, MaybeUninit<U>, PoolAlloc>, Error> {
        Self::uninit_in::<U>(PoolAlloc)
    }
    pub fn uninit_slice<U>(
        len: usize,
    ) -> Result<Boxed<'static, [MaybeUninit<U>], PoolAlloc>, Error> {
        Self::uninit_slice_in::<U>(PoolAlloc, len)
    }
}

impl Boxed<'static, [u8], PoolAlloc> {
    pub fn new_buf(layout: Layout) -> Result<Self, Error> {
        Self::new_buf_then(layout, |_| Ok(()))
    }

    pub fn new_buf_then<F>(layout: Layout, f: F) -> Result<Self, Error>
    where
        F: FnOnce(NonNull<[u8]>) -> Result<(), Error>,
    {
        Self::new_buf_then_in(PoolAlloc, layout, f)
    }
}

impl<T> Boxed<'static, [T], PoolAlloc> {
    pub fn new_slice(len: usize, val: T) -> Result<Self, Error>
    where
        T: Clone,
    {
        Self::new_slice_in(PoolAlloc, len, val)
    }

    pub fn new_slice_then<F>(len: usize, f: F) -> Result<Self, Error>
    where
        F: FnMut(usize, &mut MaybeUninit<T>) -> Result<(), Error>,
    {
        Self::new_slice_then_in(PoolAlloc, len, f)
    }
}

impl<T, A: Allocator> Boxed<'_, T, A> {
    pub fn new_in(alloc: A, val: T) -> Result<Self, Error> {
        let ptr = unsafe { alloc.init(val)? };
        Ok(unsafe { Self::from_with(alloc, ptr, Layout::new::<T>()) })
    }

    pub fn new_then_in<F>(alloc: A, f: F) -> Result<Self, Error>
    where
        F: FnOnce(&mut MaybeUninit<T>) -> Result<(), Error>,
    {
        let ptr = unsafe { alloc.alloc_then(f)? };
        Ok(unsafe { Self::from_with(alloc, ptr, Layout::new::<T>()) })
    }

    pub fn new_with_buf_in<F>(alloc: A, layout: Layout, f: F) -> Result<Self, Error>
    where
        F: FnOnce(&mut MaybeUninit<T>, NonNull<[u8]>) -> Result<(), Error>,
    {
        let (all_layout, offset) = Layout::new::<T>()
            .extend(layout)
            .map_err(|_| Error::new(EINVAL))?;
        Boxed::new_buf_then_in(alloc, all_layout, |ptr| {
            let buf = NonNull::from(&unsafe { ptr.as_ref() }[offset..]);
            let this = unsafe { ptr.cast::<MaybeUninit<T>>().as_mut() };
            f(this, buf)
        })
        .map(|boxed| unsafe { boxed.cast_unchecked::<T>() })
    }
}

impl<A: Allocator> Boxed<'_, (), A> {
    pub fn init_then_in<U, F>(alloc: A, f: F) -> Result<Boxed<'static, U, A>, Error>
    where
        F: FnOnce(&mut MaybeUninit<U>) -> Result<(), Error>,
    {
        Boxed::new_then_in(alloc, f)
    }
    pub fn init_slice_then_in<U, F>(
        alloc: A,
        len: usize,
        f: F,
    ) -> Result<Boxed<'static, [U], A>, Error>
    where
        F: FnMut(usize, &mut MaybeUninit<U>) -> Result<(), Error>,
    {
        Boxed::new_slice_then_in(alloc, len, f)
    }
    pub fn init_with_buf_in<U, F>(
        alloc: A,
        layout: Layout,
        f: F,
    ) -> Result<Boxed<'static, U, A>, Error>
    where
        F: FnOnce(&mut MaybeUninit<U>, NonNull<[u8]>) -> Result<(), Error>,
    {
        Boxed::new_with_buf_in(alloc, layout, f)
    }
    pub fn zeroed_in<'a, U>(alloc: A) -> Result<Boxed<'a, MaybeUninit<U>, A>, Error> {
        let ptr = unsafe { alloc.zeroed::<U>()? };
        Ok(unsafe { Boxed::from_with(alloc, ptr, Layout::new::<U>()) })
    }
    pub fn zeroed_slice_in<'a, U>(
        alloc: A,
        len: usize,
    ) -> Result<Boxed<'a, [MaybeUninit<U>], A>, Error> {
        let ptr = unsafe { alloc.zeroed_slice::<U>(len)? };
        Ok(unsafe { Boxed::from_with(alloc, ptr, Layout::array::<U>(len).unwrap()) })
    }
    pub fn uninit_in<'a, U>(alloc: A) -> Result<Boxed<'a, MaybeUninit<U>, A>, Error> {
        let ptr = unsafe { alloc.uninit::<U>()? };
        Ok(unsafe { Boxed::from_with(alloc, ptr, Layout::new::<U>()) })
    }
    pub fn uninit_slice_in<'a, U>(
        alloc: A,
        len: usize,
    ) -> Result<Boxed<'a, [MaybeUninit<U>], A>, Error> {
        let ptr = unsafe { alloc.uninit_slice::<U>(len)? };
        Ok(unsafe { Boxed::from_with(alloc, ptr, Layout::array::<U>(len).unwrap()) })
    }
}

impl<A: Allocator> Boxed<'_, [u8], A> {
    pub fn new_buf_in(alloc: A, layout: Layout) -> Result<Self, Error> {
        Self::new_buf_then_in(alloc, layout, |_| Ok(()))
    }

    pub fn new_buf_then_in<F>(alloc: A, layout: Layout, f: F) -> Result<Self, Error>
    where
        F: FnOnce(NonNull<[u8]>) -> Result<(), Error>,
    {
        let ptr = unsafe { alloc.alloc_buf(layout, f)? };
        Ok(unsafe { Self::from_with(alloc, ptr, layout) })
    }
}

impl<T, A: Allocator> Boxed<'_, [T], A> {
    pub fn new_slice_in(alloc: A, len: usize, val: T) -> Result<Self, Error>
    where
        T: Clone,
    {
        let ptr = unsafe { alloc.init_slice(len, val)? };
        Ok(unsafe { Self::from_with(alloc, ptr, Layout::array::<T>(len).unwrap()) })
    }

    pub fn new_slice_then_in<F>(alloc: A, len: usize, f: F) -> Result<Self, Error>
    where
        F: FnMut(usize, &mut MaybeUninit<T>) -> Result<(), Error>,
    {
        let ptr = unsafe { alloc.alloc_slice_then(len, f)? };
        Ok(unsafe { Self::from_with(alloc, ptr, Layout::array::<T>(len).unwrap()) })
    }
}

impl<T> Boxed<'_, T, NullAlloc> {
    /// # Safety
    /// data is not null.
    pub unsafe fn from_raw(data: *mut T) -> Self {
        Self {
            ptr: NonNull::new(data).unwrap(),
            layout: Layout::new::<T>(),
            alloc: NullAlloc,
            mark1: PhantomData,
            mark2: PhantomData,
        }
    }
}

impl<T> Boxed<'_, [T], NullAlloc> {
    /// # Safety
    /// data and len should be valid slice
    pub unsafe fn from_raw_slice(data: *mut T, len: usize) -> Self {
        let slice = unsafe { slice::from_raw_parts(data, len) };
        Self {
            ptr: NonNull::from(slice),
            layout: Layout::array::<T>(len).unwrap(),
            alloc: NullAlloc,
            mark1: PhantomData,
            mark2: PhantomData,
        }
    }
}

impl<'a, T, A: Allocator> Boxed<'a, MaybeUninit<T>, A> {
    pub fn write(mut self, val: T) -> Boxed<'a, T, A> {
        let _ = (*self).write(val);
        unsafe { Self::cast_unchecked::<T>(self) }
    }
}

impl<'a, T, A: Allocator> Boxed<'a, [MaybeUninit<T>], A> {
    pub fn write_slice_then<F>(mut self, mut f: F) -> Result<Boxed<'a, [T], A>, Error>
    where
        F: FnMut(usize, &mut MaybeUninit<T>) -> Result<(), Error>,
    {
        for (n, uninit) in self.iter_mut().enumerate() {
            if let Err(e) = f(n, uninit) {
                for uninit in &mut self[0..n] {
                    unsafe {
                        uninit.assume_init_drop();
                    }
                }
                return Err(e);
            }
        }
        let len = self.len();
        Ok(unsafe { Self::cast_slice_unchecked::<T>(self, len) })
    }
}

impl<'a, T: ?Sized, A: Allocator> Boxed<'a, T, A> {
    pub(crate) unsafe fn from_with(alloc: A, ptr: NonNull<T>, layout: Layout) -> Self {
        Self {
            ptr,
            layout,
            alloc,
            mark1: PhantomData,
            mark2: PhantomData,
        }
    }

    pub const fn as_ptr(&self) -> *mut T {
        self.ptr.as_ptr()
    }

    /// # Safety
    /// 使用人员需要保证类型内存布局相同
    pub unsafe fn as_other<U>(&self) -> &U {
        unsafe { self.ptr.cast::<U>().as_ref() }
    }

    /// # Safety
    /// 使用人员需要保证类型内存布局相同
    pub unsafe fn as_other_mut<U>(&mut self) -> &mut U {
        unsafe { self.ptr.cast::<U>().as_mut() }
    }

    /// # Safety
    /// 使用人员需要保证转换后类型/长度的正确性
    pub unsafe fn as_slice<U>(&self, len: usize) -> &[U] {
        unsafe { slice::from_raw_parts(self.as_other::<U>(), len) }
    }

    /// # Safety
    /// 使用人员需要保证转换后类型/长度的正确性
    pub unsafe fn as_slice_mut<U>(&mut self, len: usize) -> &mut [U] {
        unsafe { slice::from_raw_parts_mut(self.as_other_mut::<U>(), len) }
    }

    /// # Safety
    /// 使用人员需要保证U/T的内存布局相同
    pub unsafe fn cast<U>(self) -> Result<Boxed<'a, U, A>, Error> {
        let layout = Layout::new::<U>();
        if layout.size() <= self.layout.size() {
            Ok(self.cast_unchecked::<U>())
        } else {
            Err(Error::new(EINVAL))
        }
    }

    /// # Safety
    /// 使用人员需要保证U/T的内存布局相同
    pub unsafe fn cast_unchecked<U>(self) -> Boxed<'a, U, A> {
        let this = ManuallyDrop::new(self);
        let alloc = unsafe { ptr::addr_of!(this.alloc).read() };
        let ptr = NonNull::from(unsafe { this.as_other::<U>() });
        Boxed::from_with(alloc, ptr, this.layout)
    }

    /// # Safety
    /// 使用人员需要保证U/T的内存布局相同
    pub unsafe fn cast_slice<U>(self, len: usize) -> Result<Boxed<'a, [U], A>, Error> {
        let layout = Layout::array::<U>(len).map_err(|_| Error::new(EINVAL))?;
        if layout.size() <= self.layout.size() {
            Ok(self.cast_slice_unchecked::<U>(len))
        } else {
            Err(Error::new(EINVAL))
        }
    }

    /// # Safety
    /// 使用人员需要保证U/T的内存布局相同
    pub unsafe fn cast_slice_unchecked<U>(self, len: usize) -> Boxed<'a, [U], A> {
        let this = ManuallyDrop::new(self);
        let alloc = ptr::addr_of!(this.alloc).read();
        let ptr = NonNull::from(this.as_slice::<U>(len));
        Boxed::from_with(alloc, ptr, this.layout)
    }

    pub fn leak(self) -> &'a mut T {
        unsafe { ManuallyDrop::new(self).ptr.as_mut() }
    }

    pub fn into(self) -> Boxed<'a, T, NullAlloc> {
        let this = ManuallyDrop::new(self);
        unsafe { Boxed::from_with(NullAlloc, NonNull::from(this.as_ref()), this.layout) }
    }
}

impl<T: ?Sized, A: Allocator> Drop for Boxed<'_, T, A> {
    #[inline]
    fn drop(&mut self) {
        if needs_drop::<T>() {
            unsafe { ptr::drop_in_place(self.ptr.as_ptr()) };
        }
        unsafe { self.alloc.release_with(self.ptr, self.layout) };
    }
}

impl<T: ?Sized, A: Allocator> AsRef<T> for Boxed<'_, T, A> {
    #[inline(always)]
    fn as_ref(&self) -> &T {
        // # Safety
        // 构造保证指针有效
        unsafe { self.ptr.as_ref() }
    }
}

impl<T: ?Sized, A: Allocator> AsMut<T> for Boxed<'_, T, A> {
    #[inline(always)]
    fn as_mut(&mut self) -> &mut T {
        // # Safety
        // 构造保证指针有效
        unsafe { self.ptr.as_mut() }
    }
}

impl<T: ?Sized, A: Allocator> Unpin for Boxed<'_, T, A> {}

impl<T: ?Sized, A: Allocator> Deref for Boxed<'_, T, A> {
    type Target = T;
    #[inline]
    fn deref(&self) -> &Self::Target {
        // #Safety self.ptr always inited
        unsafe { self.ptr.as_ref() }
    }
}

impl<T: ?Sized, A: Allocator> DerefMut for Boxed<'_, T, A> {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        // #Safety self.ptr always inited
        unsafe { self.ptr.as_mut() }
    }
}

impl<T: ?Sized + Display, A: Allocator> Display for Boxed<'_, T, A> {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
        Display::fmt(&**self, f)
    }
}

impl<T: ?Sized + Debug, A: Allocator> Debug for Boxed<'_, T, A> {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
        Debug::fmt(&**self, f)
    }
}

impl<T: ?Sized, A: Allocator> fmt::Pointer for Boxed<'_, T, A> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let ptr: *const T = &**self;
        fmt::Pointer::fmt(&ptr, f)
    }
}

impl<T: ?Sized + PartialEq, A: Allocator> PartialEq for Boxed<'_, T, A> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        PartialEq::eq(&**self, &**other)
    }
}

impl<T: ?Sized + PartialOrd, A: Allocator> PartialOrd for Boxed<'_, T, A> {
    #[inline]
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        PartialOrd::partial_cmp(&**self, &**other)
    }
    #[inline]
    fn lt(&self, other: &Self) -> bool {
        PartialOrd::lt(&**self, &**other)
    }
    #[inline]
    fn le(&self, other: &Self) -> bool {
        PartialOrd::le(&**self, &**other)
    }
    #[inline]
    fn gt(&self, other: &Self) -> bool {
        PartialOrd::gt(&**self, &**other)
    }
    #[inline]
    fn ge(&self, other: &Self) -> bool {
        PartialOrd::ge(&**self, &**other)
    }
}

impl<T: ?Sized + Ord, A: Allocator> Ord for Boxed<'_, T, A> {
    #[inline]
    fn cmp(&self, other: &Self) -> Ordering {
        Ord::cmp(&**self, &**other)
    }
}

impl<T: ?Sized + Eq, A: Allocator> Eq for Boxed<'_, T, A> {}

impl<T: ?Sized + Hash, A: Allocator> Hash for Boxed<'_, T, A> {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        Hash::hash(&**self, state)
    }
}

impl<T: ?Sized + Hasher, A: Allocator> Hasher for Boxed<'_, T, A> {
    #[inline]
    fn finish(&self) -> u64 {
        Hasher::finish(&**self)
    }
    #[inline]
    fn write(&mut self, bytes: &[u8]) {
        Hasher::write(&mut **self, bytes)
    }
    #[inline]
    fn write_u8(&mut self, data: u8) {
        Hasher::write_u8(&mut **self, data)
    }
    #[inline]
    fn write_u16(&mut self, data: u16) {
        Hasher::write_u16(&mut **self, data)
    }
    #[inline]
    fn write_u32(&mut self, data: u32) {
        Hasher::write_u32(&mut **self, data)
    }
    #[inline]
    fn write_u64(&mut self, data: u64) {
        Hasher::write_u64(&mut **self, data)
    }
    #[inline]
    fn write_u128(&mut self, data: u128) {
        Hasher::write_u128(&mut **self, data)
    }
    #[inline]
    fn write_usize(&mut self, data: usize) {
        Hasher::write_usize(&mut **self, data)
    }
    #[inline]
    fn write_isize(&mut self, data: isize) {
        Hasher::write_isize(&mut **self, data)
    }
    #[inline]
    fn write_i8(&mut self, data: i8) {
        Hasher::write_i8(&mut **self, data)
    }
    #[inline]
    fn write_i16(&mut self, data: i16) {
        Hasher::write_i16(&mut **self, data)
    }
    #[inline]
    fn write_i32(&mut self, data: i32) {
        Hasher::write_i32(&mut **self, data)
    }
    #[inline]
    fn write_i64(&mut self, data: i64) {
        Hasher::write_i64(&mut **self, data)
    }
    #[inline]
    fn write_i128(&mut self, data: i128) {
        Hasher::write_i128(&mut **self, data)
    }
    #[cfg(feature = "nightly")]
    #[inline]
    fn write_str(&mut self, s: &str) {
        Hasher::write_str(&mut **self, s)
    }
    #[cfg(feature = "nightly")]
    #[inline]
    fn write_length_prefix(&mut self, len: usize) {
        Hasher::write_length_prefix(&mut **self, len)
    }
}

unsafe impl<T: ?Sized + Allocator, A: Allocator> Allocator for Boxed<'_, T, A> {
    unsafe fn alloc_buf<F>(&self, layout: Layout, f: F) -> Result<NonNull<[u8]>, Error>
    where
        F: FnOnce(NonNull<[u8]>) -> Result<(), Error>,
    {
        Allocator::alloc_buf(&**self, layout, f)
    }

    /// # Safety
    /// ptr一定时alloc_buf分配的内存地址
    unsafe fn deallocate(&self, ptr: NonNull<[u8]>, layout: Layout) {
        Allocator::deallocate(&**self, ptr, layout)
    }
}

#[cfg(test)]
mod test {
    extern crate std;
    use std::format;
    use std::string::ToString;

    use super::Boxed;
    use std::collections::HashSet;
    #[test]
    fn test_drop() {
        struct Foo;
        static mut DROP: usize = 0;
        impl Drop for Foo {
            fn drop(&mut self) {
                unsafe {
                    DROP += 1;
                }
            }
        }
        unsafe {
            DROP = 0;
        }

        let mut foo = Foo;
        {
            let _ = unsafe { Boxed::from_raw(&mut foo) };
        }

        unsafe {
            assert_eq!(1, DROP);
        }
    }

    #[test]
    fn test_hash() {
        let mut val = 100;
        let mut other = 100;
        let pbox2 = unsafe { Boxed::from_raw(&mut other) };
        let pbox1 = unsafe { Boxed::from_raw(&mut val) };
        let mut set = HashSet::new();
        let ret = set.insert(pbox1);
        assert!(ret);
        let ret = set.insert(pbox2);
        assert!(!ret);
    }
    #[test]
    fn test_deref() {
        let mut val = 1;
        {
            let mut pbox = unsafe { Boxed::from_raw(&mut val) };
            assert_eq!(1, *pbox);
            *pbox = 100;
            let s = format!("val = {}", pbox);
            assert_eq!(s, "val = 100".to_string());
            let s = format!("val = {:?}", pbox);
            assert_eq!(s, "val = 100".to_string());
        }
        assert_eq!(val, 100);
    }
}
